package com.linkhub.oms.component.mp.util;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.linkhub.oms.common.constant.SymbolConstant;
import com.linkhub.oms.common.util.QtBeanUtil;
import com.linkhub.oms.component.mp.enums.MpUpdateTypeEnum;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.ibatis.reflection.MetaObject;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.ObjIntConsumer;
import java.util.function.ObjLongConsumer;

/**
 * @Description: QtMybatisPlusUtil
 * @Author: echo
 * @Date: 2024/05/18 16:17
 * @Version: 1.0
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class QtMpUpdateAssist {

    /**
     * 表示为空的数字，在数据更新时，如果为该值，则将字段置空
     */
    public static final BigDecimal INDICATE_NULL_NUMBER = BigDecimal.valueOf(Long.MIN_VALUE);
    /**
     * 表示为空的日期，在数据更新时，如果为该值，则将字段置空
     */
    public static final LocalDateTime INDICATE_NULL_DATE_TIME = LocalDateTime.of(LocalDate.MIN, LocalTime.MIN);
    /**
     * 表示为空的日期，在数据更新时，如果为该值，则将字段置空
     */
    public static final LocalDate INDICATE_NULL_DATE = LocalDate.MIN;
    /**
     * 表示为空的long型数据，在数据更新时，如果为该值，则将字段置空
     */
    public static final Long INDICATE_NULL_LONG = Long.MIN_VALUE;
    /**
     * 表示为空的Integer型数据，在数据更新时，如果为该值，则将字段置空
     */
    public static final Integer INDICATE_NULL_INTEGER = -1;

    /**
     * 将字符串转化为要更新的数字，空串会转化成指定数字，表示将数据库对应字段更新为空
     */
    public static BigDecimal convertNumForUpdate( String numStr ) {
        return convertNumForUpdate(numStr, INDICATE_NULL_NUMBER);
    }

    /**
     * 将字符串转化为要更新的数字，空串会转化成指定数字，表示将数据库对应字段更新为空
     */
    public static BigDecimal convertNumForUpdate( String numStr, BigDecimal setNullValue ) {
        // 空值不作处理
        if (numStr == null) {
            return null;
        }

        // 空串设置表示空的数字
        if (numStr.trim().length() == 0) {
            return setNullValue;
        }

        return Convert.toBigDecimal(numStr);
    }

    /**
     * 将数字转化为要更新的字符串，若值为{@link QtMpUpdateAssist#INDICATE_NULL_NUMBER}会转化成空串，表示将数据库对应字段更新为空
     */
    public static String convertNumStrForUpdate( BigDecimal num ) {
        // 空值不作处理
        if (num == null) {
            return null;
        }

        // 判断是否需要返回空串
        if (isUpdateToNull(num)) {
            return "";
        }

        return QtMpUpdateAssist.stripToString(num);
    }

    /**
     * 将字符串转化为要更新的日期，空串会转化成指定日期，表示将数据库对应字段更新为空
     */
    public static LocalDateTime convertDateTimeForUpdate( String dateStr ) {
        // 空值不作处理
        if (dateStr == null) {
            return null;
        }

        // 空串设置表示空的数字
        if (dateStr.trim().length() == 0) {
            return INDICATE_NULL_DATE_TIME;
        }

        return Convert.toLocalDateTime(dateStr);
    }

    /**
     * 将字符串转化为要更新的日期，空串会转化成指定日期，表示将数据库对应字段更新为空
     */
    public static LocalDate convertDateForUpdate( String dateStr ) {
        // 空值不作处理
        if (dateStr == null) {
            return null;
        }

        // 空串设置表示空的数字
        if (dateStr.trim().length() == 0) {
            return INDICATE_NULL_DATE;
        }

        return Optional.ofNullable(Convert.toLocalDateTime(dateStr))
                .map(LocalDateTime::toLocalDate)
                .orElse(null);
    }

    /**
     * 将字符串转化为要更新的数字，空串会转化成指定数字，表示将数据库对应字段更新为空
     */
    public static Integer convertIntegerForUpdate( String integerStr ) {
        // 空值不作处理
        if (integerStr == null) {
            return null;
        }

        // 空串设置表示空的数字
        if (integerStr.trim().length() == 0) {
            return INDICATE_NULL_INTEGER;
        }

        return Integer.valueOf(integerStr);
    }

    /**
     * 若入参为{@code null}，则返回表示更新的默认值
     */
    public static String acquireStringIfNull( String obj ) {
        return ObjectUtil.defaultIfNull(obj, "");
    }

    /**
     * 若入参为{@code null}，则返回表示更新的默认值
     */
    public static BigDecimal acquireBigDecimalIfNull( BigDecimal obj ) {
        return ObjectUtil.defaultIfNull(obj, INDICATE_NULL_NUMBER);
    }

    /**
     * 若入参为{@code null}，则返回表示更新的默认值
     */
    public static LocalDateTime acquireLocalDateTimeIfNull( LocalDateTime obj ) {
        return ObjectUtil.defaultIfNull(obj, INDICATE_NULL_DATE_TIME);
    }

    /**
     * 若入参为{@code null}，则返回表示更新的默认值
     */
    public static LocalDate acquireLocalDateIfNull( LocalDate obj ) {
        return ObjectUtil.defaultIfNull(obj, INDICATE_NULL_DATE);
    }

    /**
     * 若入参为{@code null}，则返回表示更新的默认值
     */
    public static Long acquireLongIfNull( Long obj ) {
        return ObjectUtil.defaultIfNull(obj, INDICATE_NULL_LONG);
    }

    /**
     * 若入参为{@code null}，则返回表示更新的默认值
     */
    public static Integer acquireIntegerIfNull( Integer obj ) {
        return ObjectUtil.defaultIfNull(obj, INDICATE_NULL_INTEGER);
    }

    /**
     * 判断值是否为需要将数据库对应字段更新为空
     */
    public static boolean isUpdateToNull( Object obj ) {
        if (obj == null) {
            return false;
        }

        MpUpdateTypeEnum mpUpdateType = MpUpdateTypeEnum.acquireByClass(obj.getClass());
        if (mpUpdateType == null) {
            return false;
        }

        return obj.equals(mpUpdateType.getIndicateNullValue());
    }

    /**
     * 清除所有表示值为空的字段
     */
    public static <T> void clearIndicateNullValue( T saveRecord ) {
        Objects.requireNonNull(saveRecord);
        Field[] fields = ReflectUtil.getFields(saveRecord.getClass());
        for (Field field : fields) {
            Object fieldValue = ReflectUtil.getFieldValue(saveRecord, field);

            if (!QtMybatisPlusUtil.containColumnAnnotation(field) || fieldValue == null) {
                continue;
            }

            boolean toNullFlag = QtMpUpdateAssist.isUpdateToNull(fieldValue);

            if (toNullFlag) {
                ReflectUtil.setFieldValue(saveRecord, field, null);
            }
        }
    }

    /**
     * 构造债券链式更新对象
     */
    public static <T> LambdaUpdateChainWrapper<T> buildUpdateWrapper( LambdaUpdateChainWrapper<T> updateChainWrapper, T entity ) {
        Field[] fields = ReflectUtil.getFields(entity.getClass());
        for (Field field : fields) {
            Object fieldValue = ReflectUtil.getFieldValue(entity, field);

            if (!QtMybatisPlusUtil.containColumnAnnotation(field) || fieldValue == null) {
                continue;
            }

            boolean toNullFlag = QtMpUpdateAssist.isUpdateToNull(fieldValue);

            if (toNullFlag) {
                // 字段名称
                String columnName = QtMybatisPlusUtil.getColumnName(field);
                updateChainWrapper.setSql(String.format("%s = null", columnName));
                ReflectUtil.setFieldValue(entity, field, null);
            }
        }

        return updateChainWrapper;
    }


    /**
     * 更新表数据，如果字段为特定值如{@link QtMpUpdateAssist#INDICATE_NULL_DATE}，则将其置空
     * <p>
     * MP更新时主键默认会置空，不会进行更新，故不再手动将主键字段置空
     * </p>
     *
     * @param entity    实体对象
     * @param service   业务类
     * @param condition 查询条件
     * @param <T>       实体泛型
     */
    public static <T> void updateWithSetNull( T entity,
                                              IService<T> service,
                                              Consumer<LambdaUpdateChainWrapper<T>> condition ) {
        if (entity == null) {
            return;
        }

        if (QtBeanUtil.isEmptyEntity(entity)) {
            return;
        }

        LambdaUpdateChainWrapper<T> chainWrapper = service.lambdaUpdate();
        condition.accept(chainWrapper);

        LambdaUpdateChainWrapper<T> updateWrapper = QtMpUpdateAssist.buildUpdateWrapper(chainWrapper, entity);
        updateWrapper.update(entity);
    }

    /**
     * 根据主键id更新表数据，如果字段为特定值，比如{@link QtMpUpdateAssist#INDICATE_NULL_DATE}，则将其置空
     *
     * @param entity           实体对象
     * @param service          业务类
     * @param funGetPrimaryKey 主键
     * @param <T>              实体泛型
     */
    public static <T> void updateByIdWithSetNull( T entity,
                                                  IService<T> service,
                                                  SFunction<T, Long> funGetPrimaryKey ) {
        updateWithSetNull(entity, service, wrapper -> wrapper.eq(funGetPrimaryKey, funGetPrimaryKey.apply(entity)));
    }

    /**
     * 根据指定字段更新表数据，如果字段为特定值，比如{@link QtMpUpdateAssist#INDICATE_NULL_DATE}，则将其置空
     *
     * @param entity            实体对象
     * @param service           业务类
     * @param funGetPrimaryKeys 主键
     * @param <T>               实体泛型
     */
    public static <T> void updateByIdsWithSetNull( T entity,
                                                  IService<T> service,
                                                  SFunction<T, ?>... funGetPrimaryKeys ) {
        updateWithSetNull(entity, service, wrapper -> {
            for (SFunction<T, ?> funGetPrimaryKey : funGetPrimaryKeys) {
                wrapper.eq(funGetPrimaryKey, funGetPrimaryKey.apply(entity));
            }
        });
    }

    /**
     * 判断源对象是否属于指定类
     *
     * @param clazz      指定类
     * @param metaObject 源对象
     */
    public static boolean isAssignableFrom( Class<?> clazz, MetaObject metaObject ) {
        return metaObject.getOriginalObject().getClass().isAssignableFrom(clazz);
    }

    /**
     * 输出为字符串，去除末尾无效的0
     *
     * @param num 十进制数字
     * @return 输出结果
     */
    public static String stripToString( BigDecimal num ) {
        return Optional.ofNullable(num)
                .map(BigDecimal::stripTrailingZeros)
                .map(BigDecimal::toPlainString)
                .orElse(null);
    }

    /**
     * 如果字符串为空串，则设置对应的convert字段为{@link QtMpUpdateAssist#INDICATE_NULL_NUMBER}
     */
    public static <T> void setBigDecimalForUpdate( T obj, Function<T, String> funcGetDecimalStr, BiConsumer<T, BigDecimal> consSetDecimal ) {
        if (Objects.equals(funcGetDecimalStr.apply(obj), SymbolConstant.EMPTY)) {
            consSetDecimal.accept(obj, INDICATE_NULL_NUMBER);
        }
    }

    /**
     * 如果字符串为空串，则设置对应的convert字段为{@link QtMpUpdateAssist#INDICATE_NULL_DATE}
     */
    public static <T> void setDateForUpdate( T obj, Function<T, String> funcGetDateStr, BiConsumer<T, LocalDate> consSetDate ) {
        if (Objects.equals(funcGetDateStr.apply(obj), SymbolConstant.EMPTY)) {
            consSetDate.accept(obj, INDICATE_NULL_DATE);
        }
    }

    /**
     * 如果字符串为空串，则设置对应的convert字段为{@link QtMpUpdateAssist#INDICATE_NULL_DATE_TIME}
     */
    public static <T> void setDateTimeForUpdate( T obj, Function<T, String> funcGetDateStr, BiConsumer<T, LocalDateTime> consSetDate ) {
        if (Objects.equals(funcGetDateStr.apply(obj), SymbolConstant.EMPTY)) {
            consSetDate.accept(obj, INDICATE_NULL_DATE_TIME);
        }
    }

    /**
     * 如果字符串为空串，则设置对应的convert字段为{@link QtMpUpdateAssist#INDICATE_NULL_INTEGER}
     */
    public static <T> void setIntegerForUpdate( T obj, Function<T, String> funcGetVal, ObjIntConsumer<T> funcSetVal ) {
        if (Objects.equals(funcGetVal.apply(obj), SymbolConstant.EMPTY)) {
            funcSetVal.accept(obj, INDICATE_NULL_INTEGER);
        }
    }

    /**
     * 如果字符串为空串，则设置对应的convert字段为{@link QtMpUpdateAssist#INDICATE_NULL_LONG}
     */
    public static <T> void setLongForUpdate( T obj, Function<T, String> funcGetVal, ObjLongConsumer<T> funcSetVal ) {
        if (Objects.equals(funcGetVal.apply(obj), SymbolConstant.EMPTY)) {
            funcSetVal.accept(obj, INDICATE_NULL_LONG);
        }
    }

    /**
     * 数值非空判断
     */
    public static boolean nonNull( Object obj ) {
        if (Objects.isNull(obj)) {
            return false;
        }

        MpUpdateTypeEnum mpUpdateType = MpUpdateTypeEnum.acquireByClass(obj.getClass());
        if (mpUpdateType == null) {
            return true;
        }

        return !Objects.equals(obj, mpUpdateType.getIndicateNullValue());
    }

    /**
     * 数值空判断
     */
    public static boolean isNull( Object obj ) {
        return !nonNull(obj);
    }
}
